#include "vlc_player.h"
#include "vlc/libvlc_media.h"
#include "vlc/libvlc_media_player.h"
#include "aki/jsbind.h"
#include "xcomponent_render_manager.h"
#include <unistd.h>

VlcPlayer::VlcPlayer(std::string mediaURL, std::string id ,int32_t width, int32_t height, std::vector<std::string> args)
: mediaURL_(mediaURL), args_(args), width_(width), height_(height), id_(id)
{
}

VlcPlayer::~VlcPlayer()
{
    if (player_ != nullptr) {
        libvlc_media_player_stop(player_);
        libvlc_media_player_release(player_);
    }
    if (instance_ != nullptr) {
        libvlc_release(instance_);
    }
}

int32_t VlcPlayer::Init()
{
    this->videoRenderUptr_ = std::make_unique<VideoRender>(this->width_, this->height_, "I420", this->id_);
    this->audioRenderUptr_ = std::make_unique<AudioRender>();
    this->videoRenderUptr_->Init();
    this->audioRenderUptr_->Init();
    instance_ = libvlc_new(0, nullptr);
    libvlc_media_t *media = libvlc_media_new_location(instance_, this->mediaURL_.c_str());
    this->player_ = libvlc_media_player_new_from_media(media);
    AKI_LOG(INFO) << "mediaURL : " << this->mediaURL_;
    libvlc_video_set_format_callbacks(this->player_, VlcPlayer::RenderCallbacks::VideoFormatCB, nullptr);
    libvlc_audio_set_format_callbacks(this->player_, VlcPlayer::RenderCallbacks::AudioSetupCB, nullptr);

    libvlc_video_set_callbacks(this->player_, VlcPlayer::RenderCallbacks::VideoLock, \
                                VlcPlayer::RenderCallbacks::VideoUnlock, \
                                VlcPlayer::RenderCallbacks::VideoDisplay, this->videoRenderUptr_.get());
    libvlc_audio_set_callbacks(this->player_, VlcPlayer::RenderCallbacks::AudioPlay, \
                                VlcPlayer::RenderCallbacks::AudioPause, \
                                VlcPlayer::RenderCallbacks::AudioResume, \
                                VlcPlayer::RenderCallbacks::AudioFlush, \
                                VlcPlayer::RenderCallbacks::AudioDrain, this->audioRenderUptr_.get());
    libvlc_media_player_set_media(this->player_, media);
    libvlc_media_release(media);
    return 0;
}

int32_t VlcPlayer::Play()
{
    if (player_ == nullptr) {
        AKI_LOG(ERROR) << "player_ is nullptr";
        return -1;
    }
    
    libvlc_state_t state = libvlc_media_player_get_state(this->player_);
    if (state == libvlc_Playing) {
        return 0;
    }
    
    if (state == libvlc_Ended) {
        libvlc_media_player_stop(player_);
    }
    
    libvlc_media_player_play(player_);
    
    return 0;
}

int32_t VlcPlayer::Pause()
{
    if (player_ != nullptr) {
        libvlc_media_player_pause(player_);
    }
    return 0;
}

int32_t VlcPlayer::Stop()
{
    if (player_ != nullptr) {
        if (audioRenderUptr_ != nullptr) {
            audioRenderUptr_->Stop();
        }
        libvlc_media_player_stop(player_);
    }
    return 0;
}

unsigned VlcPlayer::RenderCallbacks::VideoFormatCB(void **videoRender, char *frameFormat, unsigned *width, \
                                                    unsigned *height, unsigned *pitches, unsigned *lines)
{
    VideoRender *vRender = reinterpret_cast<VideoRender*>(*videoRender);
    if (vRender == nullptr) {
        AKI_LOG(ERROR) << "VideoFormatCB data is nullptr";
        return 0;
    }
    
    *width = vRender->Width();
    *height = vRender->Height();
    size_t halfWidth = (vRender->Width() + 1) / 2;
    size_t halfHeight = (vRender->Height() + 1) / 2;
    
    pitches[0] = vRender->Width();
    pitches[1] = halfWidth;
    pitches[2] = halfWidth;
    lines[0] = vRender->Height();
    lines[1] = halfHeight;
    lines[2] = halfHeight;
    memcpy(frameFormat, vRender->PixFormat().c_str(), vRender->PixFormat().size());
    
    return 1;
}

int VlcPlayer::RenderCallbacks::AudioSetupCB(void **data, char *format, unsigned *rate, unsigned *channels)
{
    AudioRender *audioRender = reinterpret_cast<AudioRender*>(*data);
    if (audioRender == nullptr) {
        AKI_LOG(ERROR) << "AudioSetupCB data is nullptr";
        return -1;
    }
    std::string formatName = std::get<0>(audioRender->SampleFormat());
    memcpy(format, formatName.c_str(), formatName.size());
    *rate = audioRender->SampleRate();
    *channels = audioRender->Channels();
    
    return 0;
}

void *VlcPlayer::RenderCallbacks::VideoLock(void *videoRender, void **planes)
{
    VideoRender *vRender = reinterpret_cast<VideoRender *>(videoRender);
    if (vRender == nullptr) {
        AKI_LOG(ERROR) << "VideoLock data is nullptr";
        return nullptr;
    }
    
    size_t ySize = vRender->Width() * vRender->Height();
    size_t uvSize = ySize / 4;
    
    planes[0] = vRender->FrameBuffer();
    planes[1] = vRender->FrameBuffer() + ySize;
    planes[2] = vRender->FrameBuffer() + ySize + uvSize;
    
    return nullptr;
}

void VlcPlayer::RenderCallbacks::VideoDisplay(void *videoRender, void *picture)
{
    VideoRender *vRender = reinterpret_cast<VideoRender *>(videoRender);
    if (vRender == nullptr) {
        AKI_LOG(ERROR) << "VideoDisplay data is nullptr";
        return;
    }
    
    if (vRender->FrameBuffer() == nullptr) {
        AKI_LOG(ERROR) << "VideoDisplay FrameBuffer is nullptr";
        return;
    }
    
    size_t ySize = vRender->Width() * vRender->Height();
    uint8_t *yData = vRender->FrameBuffer();
    uint8_t *uData = vRender->FrameBuffer() + ySize;
    uint8_t *vData = vRender->FrameBuffer() + ySize + ySize / 4;
    
    vRender->Render(yData, uData, vData, vRender->Width(), vRender->Height());
}

void VlcPlayer::RenderCallbacks::VideoUnlock(void *videoRender, void *picture, void *const *planes)
{
}

void VlcPlayer::RenderCallbacks::AudioPlay(void *audioRender, const void *samples, unsigned count, int64_t pts)
{
    AudioRender *aRender = reinterpret_cast<AudioRender *>(audioRender);
    if (aRender == nullptr) {
        AKI_LOG(ERROR) << "AudioPlay data is nullptr";
        return;
    }
    aRender->Play();
    AudioFrameSlice audioFrameSlice;
    audioFrameSlice.bufferUsed = 0;
    audioFrameSlice.pts = pts;
    audioFrameSlice.bufferLen = count * aRender->Channels() * std::get<2>(aRender->SampleFormat()) / 8;
    auto tmp = new uint8_t[audioFrameSlice.bufferLen];
    if (tmp == nullptr) {
        return;
    }
    audioFrameSlice.buffer = std::shared_ptr<uint8_t>(tmp, AudioFrameSlice::Deleter);
    memcpy(audioFrameSlice.buffer.get(), samples, audioFrameSlice.bufferLen);
    aRender->PushFrameSlice(audioFrameSlice);
}

void VlcPlayer::RenderCallbacks::AudioPause(void *audioRender, int64_t pts)
{
    AudioRender *aRender = reinterpret_cast<AudioRender *>(audioRender);
    if (aRender == nullptr) {
        AKI_LOG(ERROR) << "AudioPause data is nullptr";
        return;
    }

    aRender->Pause();
}

void VlcPlayer::RenderCallbacks::AudioResume(void *audioRender, int64_t pts)
{
    AudioRender *aRender = reinterpret_cast<AudioRender *>(audioRender);
    if (aRender == nullptr) {
        AKI_LOG(ERROR) << "AudioResume data is nullptr";
        return;
    }

    aRender->Resume();
}

void VlcPlayer::RenderCallbacks::AudioFlush(void *audioRender, int64_t pts)
{
    AudioRender *aRender = reinterpret_cast<AudioRender *>(audioRender);
    if (aRender == nullptr) {
        AKI_LOG(ERROR) << "AudioFlush data is nullptr";
        return;
    }

    aRender->Flush();
}

void VlcPlayer::RenderCallbacks::AudioDrain(void *audioRender)
{
}

libvlc_state_t VlcPlayer::State() const
{
    return state_;
}

JSBIND_CLASS(VlcPlayer)
{
    JSBIND_CONSTRUCTOR<std::string, std::string, int32_t, int32_t, std::vector<std::string>>();
    JSBIND_CONSTRUCTOR<std::string, std::string>();
    JSBIND_CONSTRUCTOR<std::string, std::string, int32_t, int32_t>();
    JSBIND_METHOD(Play);
    JSBIND_METHOD(Init);
    JSBIND_METHOD(Stop);
    JSBIND_METHOD(Pause);
    JSBIND_METHOD(State);
}
